package top.resty;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.math.BigInteger;

import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;

public class Main {

    // 标题信息类
    static class HeadingInfo {
        private String text;
        private String styleId;
        private int level;
        private XWPFParagraph paragraph;
        private String bookmarkName;
        private int pageNumber;

        public HeadingInfo(String text, String styleId, int level, XWPFParagraph paragraph) {
            this.text = text;
            this.styleId = styleId;
            this.level = level;
            this.paragraph = paragraph;
            this.bookmarkName = generateSafeBookmarkName(text);
            this.pageNumber = 1; // 默认页码
        }

        /**
         * 生成安全的书签名称
         */
        private static String generateSafeBookmarkName(String text) {
            // 移除特殊字符，只保留字母、数字和下划线
            String safeName = text.replaceAll("[^\\w\\u4e00-\\u9fa5]", "_");
            // 确保以字母开头
            if (!safeName.matches("^[a-zA-Z\\u4e00-\\u9fa5].*")) {
                safeName = "heading_" + safeName;
            }
            // 限制长度并添加唯一标识
            if (safeName.length() > 30) {
                safeName = safeName.substring(0, 30);
            }
            safeName += "_" + System.currentTimeMillis() % 10000;
            return safeName;
        }

        // Getters and Setters
        public String getText() { return text; }
        public void setText(String text) { this.text = text; }
        public String getStyleId() { return styleId; }
        public void setStyleId(String styleId) { this.styleId = styleId; }
        public int getLevel() { return level; }
        public void setLevel(int level) { this.level = level; }
        public XWPFParagraph getParagraph() { return paragraph; }
        public void setParagraph(XWPFParagraph paragraph) { this.paragraph = paragraph; }
        public String getBookmarkName() { return bookmarkName; }
        public void setBookmarkName(String bookmarkName) { this.bookmarkName = bookmarkName; }
        public int getPageNumber() { return pageNumber; }
        public void setPageNumber(int pageNumber) { this.pageNumber = pageNumber; }
    }

    public static void main(String[] args) {
        try {
            String originalDocPath = "generated_document.docx";
            String newDocPath = "generated_document_with_toc.docx";

            // 1. 获取原文档内容，如果不存在则创建示例文档
            XWPFDocument originalDocument;
            try {
                originalDocument = loadDocument(originalDocPath);
                System.out.println("成功加载原文档: " + originalDocPath);
            } catch (Exception e) {
                System.out.println("原文档不存在，创建示例文档...");
                originalDocument = createSampleDocument();
                saveDocument(originalDocument, originalDocPath);
                originalDocument = loadDocument(originalDocPath);
                System.out.println("示例文档已创建: " + originalDocPath);
            }

            // 2. 遍历出原文档中所有的标题并存进集合
            List<HeadingInfo> headings = extractHeadings(originalDocument);

            if (headings.isEmpty()) {
                System.out.println("原文档中没有标题，创建示例文档...");
                originalDocument.close();
                originalDocument = createSampleDocument();
                saveDocument(originalDocument, originalDocPath);
                originalDocument = loadDocument(originalDocPath);
                headings = extractHeadings(originalDocument);
            }

            // 3. 创建新文档，复制原文档内容
            XWPFDocument newDocument = createDocumentCopy(originalDocument);
            originalDocument.close(); // 关闭原文档，不再修改

            // 4. 在新文档中为标题添加书签
            List<HeadingInfo> newHeadings = extractHeadings(newDocument);
            addBookmarksToHeadings(newHeadings);

            // 5. 先用临时页码插入目录
            calculateTemporaryPageNumbers(newHeadings);

            // 6. 在新文档最初的位置插入目录
            insertTableOfContents(newDocument, newHeadings);

            // 7. 计算目录实际占用的页数，重新计算页码
            int tocPages = calculateTocPages(newDocument, newHeadings.size());
            recalculatePageNumbers(newHeadings, tocPages);

            // 8. 更新目录中的页码
            updateTocPageNumbers(newDocument, newHeadings);

            // 9. 保存新文档
            saveDocument(newDocument, newDocPath);

            System.out.println("新文档生成成功: " + newDocPath);
            System.out.println("原文档保持不变: " + originalDocPath);
            System.out.println("找到 " + newHeadings.size() + " 个标题");
            System.out.println("目录占用 " + tocPages + " 页");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 创建示例文档
     */
    private static XWPFDocument createSampleDocument() {
        XWPFDocument document = new XWPFDocument();

        // 创建文档标题
        XWPFParagraph titleParagraph = document.createParagraph();
        titleParagraph.setAlignment(ParagraphAlignment.CENTER);
        XWPFRun titleRun = titleParagraph.createRun();
        titleRun.setText("文档标题");
        titleRun.setBold(true);
        titleRun.setFontSize(20);
        titleRun.setFontFamily("宋体");

        // 添加空行
        document.createParagraph();

        // 创建20个一级标题，每个一级标题下有5个二级标题
        for (int i = 1; i <= 20; i++) {
            // 创建一级标题
            XWPFParagraph h1Paragraph = document.createParagraph();
            h1Paragraph.setStyle("Heading1");
            XWPFRun h1Run = h1Paragraph.createRun();
            h1Run.setText("第" + i + "章 一级标题" + i);
            h1Run.setBold(true);
            h1Run.setFontSize(16);
            h1Run.setFontFamily("宋体");

            // 在一级标题下添加内容
            XWPFParagraph contentParagraph = document.createParagraph();
            XWPFRun contentRun = contentParagraph.createRun();
            contentRun.setText("这是第" + i + "章的内容介绍。本章将详细介绍相关主题的各个方面。");
            contentRun.setFontFamily("宋体");
            contentRun.setFontSize(12);

            // 添加空行
            document.createParagraph();

            // 创建5个二级标题
            for (int j = 1; j <= 5; j++) {
                XWPFParagraph h2Paragraph = document.createParagraph();
                h2Paragraph.setStyle("Heading2");
                XWPFRun h2Run = h2Paragraph.createRun();
                h2Run.setText(i + "." + j + " 二级标题" + i + "." + j);
                h2Run.setBold(true);
                h2Run.setFontSize(14);
                h2Run.setFontFamily("宋体");

                // 在二级标题下添加内容
                XWPFParagraph subContentParagraph = document.createParagraph();
                XWPFRun subContentRun = subContentParagraph.createRun();
                subContentRun.setText("这是第" + i + "章第" + j + "节的具体内容。在这里可以添加详细的说明和描述，包括相关的技术细节、实例分析和应用场景等。");
                subContentRun.setFontFamily("宋体");
                subContentRun.setFontSize(12);

                // 添加空行
                document.createParagraph();
            }

            // 在每章之间添加分页符（除了最后一章）
            if (i < 20) {
                XWPFParagraph chapterBreakParagraph = document.createParagraph();
                XWPFRun chapterBreakRun = chapterBreakParagraph.createRun();
                chapterBreakRun.addBreak(BreakType.PAGE);
            }
        }

        System.out.println("示例文档创建完成");
        return document;
    }

    /**
     * 1. 加载文档
     */
    private static XWPFDocument loadDocument(String docPath) throws Exception {
        try (FileInputStream fis = new FileInputStream(docPath)) {
            return new XWPFDocument(fis);
        }
    }

    /**
     * 创建文档副本
     */
    private static XWPFDocument createDocumentCopy(XWPFDocument originalDocument) throws Exception {
        XWPFDocument newDocument = new XWPFDocument();

        // 复制所有段落
        for (XWPFParagraph originalParagraph : originalDocument.getParagraphs()) {
            XWPFParagraph newParagraph = newDocument.createParagraph();
            copyParagraph(originalParagraph, newParagraph);
        }

        System.out.println("文档副本创建完成，共复制 " + originalDocument.getParagraphs().size() + " 个段落");
        return newDocument;
    }

    /**
     * 复制段落内容
     */
    private static void copyParagraph(XWPFParagraph source, XWPFParagraph target) {
        // 复制段落属性
        target.setAlignment(source.getAlignment());
        target.setIndentationLeft(source.getIndentationLeft());
        target.setIndentationRight(source.getIndentationRight());
        target.setSpacingAfter(source.getSpacingAfter());
        target.setSpacingBefore(source.getSpacingBefore());

        // 复制样式
        if (source.getStyleID() != null) {
            target.setStyle(source.getStyleID());
        }

        // 复制所有运行
        for (XWPFRun sourceRun : source.getRuns()) {
            XWPFRun targetRun = target.createRun();
            copyRun(sourceRun, targetRun);
        }
    }

    /**
     * 复制运行内容
     */
    private static void copyRun(XWPFRun source, XWPFRun target) {
        // 复制文本
        String text = source.getText(0);
        if (text != null) {
            target.setText(text);
        }

        // 复制格式
        target.setBold(source.isBold());
        target.setItalic(source.isItalic());
        target.setUnderline(source.getUnderline());

        if (source.getFontFamily() != null) {
            target.setFontFamily(source.getFontFamily());
        }

        if (source.getFontSize() != -1) {
            target.setFontSize(source.getFontSize());
        }

        if (source.getColor() != null) {
            target.setColor(source.getColor());
        }

        // 复制分页符
        if (source.getCTR().getBrList().size() > 0) {
            for (int i = 0; i < source.getCTR().getBrList().size(); i++) {
                target.addBreak(BreakType.PAGE);
            }
        }
    }

    /**
     * 2. 遍历文档中所有的标题并存进集合
     */
    private static List<HeadingInfo> extractHeadings(XWPFDocument document) {
        List<HeadingInfo> headings = new ArrayList<>();

        for (XWPFParagraph paragraph : document.getParagraphs()) {
            String styleId = paragraph.getStyleID();
            String text = paragraph.getText();

            if (text != null && !text.trim().isEmpty() && styleId != null) {
                int level = getHeadingLevel(styleId);
                if (level > 0) {
                    // 过滤掉目录中的条目和只有数字的标题
                    if (isValidHeading(text.trim())) {
                        HeadingInfo heading = new HeadingInfo(text.trim(), styleId, level, paragraph);
                        headings.add(heading);
                        System.out.println("找到标题: " + text + " (级别: " + level + ")");
                    }
                }
            }
        }

        return headings;
    }

    /**
     * 判断是否为有效的标题
     */
    private static boolean isValidHeading(String text) {
        // 过滤掉纯数字的标题
        if (text.matches("^\\d+$")) {
            return false;
        }

        // 过滤掉目录标题
        if (text.equals("目录")) {
            return false;
        }

        // 确保标题包含"章"或者"."（表示章节编号）
        if (text.contains("章") || text.matches("\\d+\\.\\d+.*")) {
            return true;
        }

        return false;
    }

    /**
     * 判断样式是否为标题样式，返回标题级别
     */
    private static int getHeadingLevel(String styleId) {
        if (styleId == null) return 0;

        String lowerStyleId = styleId.toLowerCase();
        if (lowerStyleId.contains("heading1") || lowerStyleId.contains("1")) {
            return 1;
        } else if (lowerStyleId.contains("heading2") || lowerStyleId.contains("2")) {
            return 2;
        } else if (lowerStyleId.contains("heading3") || lowerStyleId.contains("3")) {
            return 3;
        }

        return 0;
    }

    /**
     * 3. 为标题添加书签
     */
    private static void addBookmarksToHeadings(List<HeadingInfo> headings) {
        long bookmarkId = 1; // 从1开始，避免ID冲突

        for (HeadingInfo heading : headings) {
            XWPFParagraph paragraph = heading.getParagraph();
            String bookmarkName = heading.getBookmarkName();

            try {
                // 清除段落中可能存在的旧书签
                clearExistingBookmarks(paragraph);

                // 添加书签开始标记（在段落开头）
                CTBookmark bookmarkStart = paragraph.getCTP().insertNewBookmarkStart(0);
                bookmarkStart.setName(bookmarkName);
                bookmarkStart.setId(BigInteger.valueOf(bookmarkId));

                // 添加书签结束标记（在段落末尾）
                CTMarkupRange bookmarkEnd = paragraph.getCTP().addNewBookmarkEnd();
                bookmarkEnd.setId(BigInteger.valueOf(bookmarkId));

                bookmarkId++;

                System.out.println("为标题添加书签: " + heading.getText() + " -> " + bookmarkName);
            } catch (Exception e) {
                System.out.println("添加书签失败: " + heading.getText() + " - " + e.getMessage());
            }
        }
    }

    /**
     * 清除段落中现有的书签
     */
    private static void clearExistingBookmarks(XWPFParagraph paragraph) {
        try {
            // 移除现有的书签开始标记
            for (int i = paragraph.getCTP().getBookmarkStartList().size() - 1; i >= 0; i--) {
                paragraph.getCTP().removeBookmarkStart(i);
            }
            // 移除现有的书签结束标记
            for (int i = paragraph.getCTP().getBookmarkEndList().size() - 1; i >= 0; i--) {
                paragraph.getCTP().removeBookmarkEnd(i);
            }
        } catch (Exception e) {
            // 忽略清除错误
        }
    }

    /**
     * 4. 计算临时页码（用于初始目录生成）
     */
    private static void calculateTemporaryPageNumbers(List<HeadingInfo> headings) {
        int currentPage = 1; // 临时从第1页开始

        for (HeadingInfo heading : headings) {
            if (heading.getLevel() == 1) {
                // 一级标题，新的一页
                currentPage++;
                heading.setPageNumber(currentPage);
            } else {
                // 二级标题，在当前页
                heading.setPageNumber(currentPage);
            }
        }
        System.out.println("临时页码计算完成");
    }

    /**
     * 计算目录占用的页数（更精确的计算）
     */
    private static int calculateTocPages(XWPFDocument document, int headingCount) {
        // 更精确的估算：
        // 1. 目录标题：1行
        // 2. 空行：1行
        // 3. 目录条目：headingCount行
        // 4. 分页符：1行
        // 假设每页可以容纳约45行（考虑页边距）
        int linesPerPage = 45;
        int totalLines = 1 + 1 + headingCount + 1; // 标题 + 空行 + 条目 + 分页符
        int tocPages = (int) Math.ceil((double) totalLines / linesPerPage);

        // 至少1页
        if (tocPages < 1) tocPages = 1;

        System.out.println("估算目录页数: " + tocPages + " 页 (共 " + headingCount + " 个条目, " + totalLines + " 行)");
        return tocPages;
    }

    /**
     * 根据目录页数重新计算页码
     */
    private static void recalculatePageNumbers(List<HeadingInfo> headings, int tocPages) {
        int currentPage = tocPages; // 内容从目录后开始

        for (HeadingInfo heading : headings) {
            if (heading.getLevel() == 1) {
                // 一级标题，新的一页
                currentPage++;
                heading.setPageNumber(currentPage);
            } else {
                // 二级标题，在当前页
                heading.setPageNumber(currentPage);
            }

            System.out.println("重新计算页码: " + heading.getText() + " -> 第" + heading.getPageNumber() + "页");
        }
    }

    /**
     * 5. 在文档最初的位置插入目录
     */
    private static void insertTableOfContents(XWPFDocument document, List<HeadingInfo> headings) {
        if (headings.isEmpty()) {
            System.out.println("没有找到标题，无法生成目录");
            return;
        }

        // 获取第一个段落的位置
        XWPFParagraph firstParagraph = document.getParagraphs().get(0);

        // 在第一个段落前插入目录标题
        XWPFParagraph tocTitleParagraph = document.insertNewParagraph(firstParagraph.getCTP().newCursor());
        tocTitleParagraph.setAlignment(ParagraphAlignment.CENTER);
        XWPFRun tocTitleRun = tocTitleParagraph.createRun();
        tocTitleRun.setText("目录");
        tocTitleRun.setBold(true);
        tocTitleRun.setFontSize(16);
        tocTitleRun.setFontFamily("宋体");

        // 添加空行
        XWPFParagraph emptyLine = document.insertNewParagraph(firstParagraph.getCTP().newCursor());

        // 为每个标题创建目录条目
        for (HeadingInfo heading : headings) {
            XWPFParagraph tocEntry = document.insertNewParagraph(firstParagraph.getCTP().newCursor());
            createTocEntryInParagraph(tocEntry, heading);
        }

        // 添加分页符
        XWPFParagraph pageBreak = document.insertNewParagraph(firstParagraph.getCTP().newCursor());
        XWPFRun pageBreakRun = pageBreak.createRun();
        pageBreakRun.addBreak(BreakType.PAGE);

        System.out.println("目录插入完成，共 " + headings.size() + " 个条目");
    }

    /**
     * 更新目录中的页码
     */
    private static void updateTocPageNumbers(XWPFDocument document, List<HeadingInfo> headings) {
        System.out.println("开始更新目录页码...");

        // 找到所有目录段落并更新页码
        List<XWPFParagraph> paragraphs = document.getParagraphs();
        int headingIndex = 0;

        for (XWPFParagraph paragraph : paragraphs) {
            // 检查段落是否包含超链接（更简单的检测方法）
            if (containsHyperlink(paragraph) && headingIndex < headings.size()) {
                HeadingInfo heading = headings.get(headingIndex);
                updateParagraphPageNumber(paragraph, heading.getPageNumber());
                System.out.println("更新页码: " + heading.getText() + " -> 第" + heading.getPageNumber() + "页");
                headingIndex++;
            }
        }

        System.out.println("目录页码更新完成");
    }

    /**
     * 检查段落是否包含超链接
     */
    private static boolean containsHyperlink(XWPFParagraph paragraph) {
        // 检查段落是否包含超链接运行
        for (XWPFRun run : paragraph.getRuns()) {
            if (run instanceof XWPFHyperlinkRun) {
                return true;
            }
        }

        // 检查段落文本是否包含标题文本（作为备选检测方法）
        String paragraphText = paragraph.getText();
        if (paragraphText != null && (paragraphText.contains("章") || paragraphText.contains("."))) {
            return true;
        }

        return false;
    }

    /**
     * 更新段落中的页码
     */
    private static void updateParagraphPageNumber(XWPFParagraph paragraph, int newPageNumber) {
        // 找到段落中最后一个文本运行（通常是页码）
        List<XWPFRun> runs = paragraph.getRuns();
        if (!runs.isEmpty()) {
            // 从后往前查找数字（页码）
            for (int i = runs.size() - 1; i >= 0; i--) {
                XWPFRun run = runs.get(i);
                String text = run.getText(0);
                if (text != null && text.matches("\\d+")) {
                    run.setText(String.valueOf(newPageNumber), 0);
                    System.out.println("  页码已更新: " + text + " -> " + newPageNumber);
                    return;
                }
            }

            // 如果没找到数字，在最后添加页码
            XWPFRun lastRun = runs.get(runs.size() - 1);
            String currentText = lastRun.getText(0);
            if (currentText != null && !currentText.matches("\\d+")) {
                lastRun.setText(String.valueOf(newPageNumber), 0);
                System.out.println("  添加页码: " + newPageNumber);
            }
        }
    }

    /**
     * 在指定段落中创建目录条目
     */
    private static void createTocEntryInParagraph(XWPFParagraph tocParagraph, HeadingInfo heading) {
        // 设置缩进（二级标题及以下缩进）
        if (heading.getLevel() > 1) {
            tocParagraph.setIndentationLeft(400 * (heading.getLevel() - 1));
        }

        // 设置制表位（右对齐，点状引导线）
        CTPPr pPr = tocParagraph.getCTP().isSetPPr() ? tocParagraph.getCTP().getPPr() : tocParagraph.getCTP().addNewPPr();
        CTTabs tabs = pPr.isSetTabs() ? pPr.getTabs() : pPr.addNewTabs();

        // 清除现有制表位
        if (tabs.getTabList().size() > 0) {
            tabs.setTabArray(null);
        }

        // 设置制表位：右对齐，点状引导线（虚线）
        CTTabStop tabStop = tabs.addNewTab();
        tabStop.setVal(STTabJc.RIGHT);
        tabStop.setLeader(STTabTlc.DOT); // 点状引导线（虚线）
        tabStop.setPos(BigInteger.valueOf(8500)); // 制表位位置

        // 创建超链接到书签
        XWPFHyperlinkRun hyperlinkRun = tocParagraph.createHyperlinkRun("#" + heading.getBookmarkName());
        hyperlinkRun.setText(heading.getText());
        hyperlinkRun.setFontFamily("宋体");
        hyperlinkRun.setFontSize(12);
        hyperlinkRun.setColor("0000FF"); // 蓝色
        hyperlinkRun.setUnderline(UnderlinePatterns.SINGLE); // 下划线

        System.out.println("创建超链接: " + heading.getText() + " -> " + heading.getBookmarkName());

        // 添加制表符（这会触发虚线填充）
        XWPFRun tabRun = tocParagraph.createRun();
        tabRun.addTab();

        // 添加页码
        XWPFRun pageRun = tocParagraph.createRun();
        pageRun.setText(String.valueOf(heading.getPageNumber()));
        pageRun.setFontFamily("宋体");
        pageRun.setFontSize(12);

        System.out.println("创建目录条目: " + heading.getText() + " -> 第" + heading.getPageNumber() + "页");
    }



    /**
     * 6. 保存文档
     */
    private static void saveDocument(XWPFDocument document, String docPath) throws Exception {
        try (FileOutputStream fos = new FileOutputStream(docPath)) {
            document.write(fos);
        }
        document.close();
        System.out.println("文档已保存: " + docPath);
    }
}